/*先考虑暴力求解两个字符串的最长公共子串的情况：
假设已经将一个字符串的所有子串求出，当前枚举另外一个字符串的开始位置，
假设枚举到某个位置时停下来，此时开始位置应该后移一位继续匹配，如何将此操作转移到后缀自动机上并且优化过程？

等价于先建立一个字符串的后缀自动机，然后在后缀自动机的有向无环图上匹配另外一个，
如果走得动，则更新到达该节点的最长路径，
否则，本来是要到减少该路径上的第一个字符表示的节点上继续匹配的，
但是如果该字符串不是该节点表示的最短子串的话，后面还是会走到该节点，而且此时路径还要比之前短，
所以当初应该直接跳向最短串减去第一个字符表示的节点上，即其父节点上，
但此时该父节点可能没有更新，由于该父节点的最长子串为其子节点的后缀，子节点中存在某一个子串的话说明父节点表示的所有串都存在，
此时父节点应该更新为该节点表示的最长串长度，即需要向上传递更新父节点为该节点的最长子串长度，
但这样每次走不动就更新的话复杂度过大，可以先不更新，处理完匹配串后，建立后缀自动机表示的树，由下向上传递最大值，
为什么是下面节点的最大值，当前节点表示的最长子串的最大值显然都要小于子节点中表示的子串的长度？

对于每一个节点而言，由于要取的是公共子串，其他所有子串走到该节点可能会形成长度不同的子串，
此时对于该节点应该取最小的子串这样才能使所有字符串都含有该子串，
所以答案节点可以初始化为节点表示的子串的最长长度，
然后当向上传递标记时传递最大值最后更新答案时对答案就不会有影响*/

#define quickread
#ifdef quickread
#include <cstdio> 
template <typename T>
inline void read(T& x)
{
    int c=getchar(), f=1; x=0;
    while(c<'0'||'9'<c) {if(c=='-') f=-1; c=getchar();}
    while('0'<=c&&c<='9') 
        x=(x<<3)+(x<<1)+c-'0', c=getchar();
    x*=f;
}
template <typename T>
inline void quickwrite(T x)
{
    if(x>=10) quickwrite(x/10);
    putchar(x%10+'0');
}
#else 
#include <iostream>
#endif
#include <cstring>
#include <algorithm>
using namespace std;
#define DEBUG
using ll=long long;
const int N=2000010;
int tot=1, last=1; //tot表示当前用到的编号，last表示上一个点的编号，1号是源点

struct Node{
    int len, fa; //len表示当前状态中所有字串的最大长度，fa表示Link边指向的状态
    int ch[26];
} node[N];//存储后缀自动机的每个状态
char str[N]; //原字符串
ll f[N]; //f[i]表示后缀自动机中状态i的endpos大小
int h[N], e[N], ne[N], idx;
int n;
int ans[N], now[N];
// ans[]用于存储在所有字符串匹配过程中，每个后缀自动机节点能匹配到的最长公共子串长度
// now[]用于临时存储当前字符串在后缀自动机各节点上的最长匹配长度
ll res;

void AddEdge(int a, int b)
{
    e[idx]=b, ne[idx]=h[a], h[a]=idx++;
}

void extend(int c)
{
    //p表示上一个插入的状态，np表示当前插入的新状态
    int p=last, np=last=++tot;
    //依次加入每个点，则每次插入得到的新状态都是一个前缀,递推时无法记录前缀，这里提前算上每个前缀在endpos中的贡献
    node[np].len=node[p].len+1; //新状态的长度= 旧状态的长度+1
    //p的末尾加上c，则p沿着Link边往下走到的所有状态都应该加上c
    for(; p&&!node[p].ch[c]; p=node[p].fa) node[p].ch[c]=np;
    if(!p) node[np].fa=1;
    else
    {
        //否则说明从当前p开始沿着Link边走到的所有状态后面都有c，此时当前的p加上c后的状态就是一个新的等价类
        //新状态no的Link应该指向当前的p加上c后的状态q
        int q=node[p].ch[c];
        //如果q的长度正好是p的长度+1，则np的Link边直接指向q
        if(node[q].len==node[p].len+1) node[np].fa=q;
        else
        {
            //否则需要拷贝一个长度是p的长度+1的新状态，再令q和np的Link边指向拷贝后的状态nq
            int nq=++tot;
            node[nq]=node[q], node[nq].len=node[p].len+1; //拷贝并修改长度
            node[q].fa=node[np].fa=nq; //令q和np的Link边指向nq
            for(;p&&node[p].ch[c]==q; p=node[p].fa) node[p].ch[c]=nq; //再将指向q的所有普通边改成指向nq
        }
    }
}

void dfs(int u) //递推endpos
{
    for(int i=h[u]; ~i; i=ne[i])
    {
        int v=e[i];
        // printf("%d-%d->%d\n", u, i, v);

        dfs(v);
        f[u]+=f[v]; //更新当前endpos
    }
    if(f[u]>1) res=max(res, f[u]*node[u].len); 
    //如果当前状态代表的字符串出现次数不为1，更新权值最大值
}

void init()
{
    idx=0;
    memset(h, -1, sizeof h);
    read(n);
    scanf("%s", str);
    for(int i=0; str[i]; i++) extend(str[i]-'a');
    memset(ans, 0x3f, sizeof ans);
    // for (int i = 1; i <= tot; i ++ ) ans[i] = node[i].len;
    // for (int i = 2; i <= tot; i ++ ) AddEdge(node[i].fa, i);

}

void PrintGraph(int u)
{
    for(int i=h[u]; ~i; i=ne[i])
    {
        int v=e[i];
        printf("i%d:[%d, len%d, fa%d]\n", i, v, node[i].len, node[i].fa);
    }
}

void solve()
{
    init();
    for(int i=0; i<n-1; i++) {
        scanf("%s", str);
        memset(now, 0, sizeof now);
        int p=1, t=0; //当前节点 匹配长度
        for(int j=0; str[j]; j++)
        {
            int c=str[j]-'a';
            while(p>1&&!node[p].ch[c]) p=node[p].fa, t=node[p].len;
            if(node[p].ch[c]) p=node[p].ch[c], t++; //有转移边 转移并更新匹配长度
            now[p]=max(now[p], t);
        }
        dfs(1);
        for(int j=1; j<=tot; j++) ans[j]=min(ans[j], now[j]);
    }
    int res=0;
    for(int i=1; i<=tot; i++) res=max(res, ans[i]);
    
    quickwrite(res); puts("");
}

// #undef DEBUG
signed main()
{
    #ifdef DEBUG
        freopen("./in.txt", "r", stdin);
        // freopen("./out.txt", "w", stdout);
    #endif
    #ifndef quickread
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    #endif

    int T=1; //scanf("%d", &T);
    while(T--) 
    {
        solve();
    }
    return 0;
}
